﻿using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace lib.dmt.teltonika.server.Parsers
{
    public class AVLDataPacket
    {
        public byte CodecId { get; set; }
        public byte NumberOfRecords1 { get; set; }
        public List<AVLData> AVLData { get; set; } = new List<AVLData>();
        public byte NumberOfRecords2 { get; set; }
        public byte[] CRC { get; set; } = new byte[16];
    }

    public class AVLData
    {
        /// <summary>
        /// this IMEI is used to determin the IMEI belongs to which device.
        /// </summary>
        public string IMEI { get; set; } = string.Empty;
        
        /// <summary>
        /// the codec, normally it is 0x8E = 142
        /// </summary>
        public byte CodecId { get; set; } = 142;
        
        /// <summary>
        /// this record received by server timestamp
        /// </summary>
        public DateTime ReceivedTime { get; set; }

        public DateTime Timestamp { get; set; }
        public byte Priority { get; set; }
        public GPSElement GPS { get; set; } = new GPSElement();
        public IOElement IO { get; set; } = new IOElement();
    }

    public class GPSElement
    {
        public double Longitude { get; set; }
        public double Latitude { get; set; }
        public short Altitude { get; set; }
        public short Angle { get; set; }
        public byte Satellites { get; set; }
        public short Speed { get; set; }
        public bool IsValid { get; set; }
    }

    public class IOElement
    {
        public short EventIOId { get; set; }
        public short TotalIO { get; set; }
        public List<IOProperty> OneByteIO { get; set; } = new List<IOProperty>();
        public List<IOProperty> TwoBytesIO { get; set; } = new List<IOProperty>();
        public List<IOProperty> FourBytesIO { get; set; } = new List<IOProperty>();
        public List<IOProperty> EightBytesIO { get; set; } = new List<IOProperty>();
        public List<IOProperty> XBytesIO { get; set; } = new List<IOProperty>();
    }

    public class IOProperty
    {
        public ushort Id { get; set; }
        //public string Name { get; set; } = string.Empty;
        public object Value { get; set; } = new object();
    }
    public class Teltonika8EParser
    {
        // Constants
        public const byte CODEC_8_ID = 0x08;
        public const byte CODEC_8_EXTENDED_ID = 0x8E;
        public const byte CODEC_16_ID = 0x10;
        public const int PREAMBLE_LENGTH = 4;
        public const int CRC_LENGTH = 4;

        public AVLDataPacket ParseAVLDataPacket(byte[] data, string imei)
        {
            using (var stream = new MemoryStream(data))
            using (var reader = new BinaryReader(stream))
            {
                // Check preamble (4 zero bytes)
                var preamble = reader.ReadBytes(PREAMBLE_LENGTH);
                if (!preamble.All(b => b == 0x00))
                {
                    throw new InvalidDataException("Invalid preamble - expected 4 zero bytes");
                }

                // Read data length
                //var dataLength = reader.ReadInt32();
                var dataLength = BinaryPrimitives.ReadInt32BigEndian(reader.ReadBytes(4));

                // Read codec ID
                var codecId = reader.ReadByte();
                if (codecId != CODEC_8_ID && codecId != CODEC_8_EXTENDED_ID && codecId != CODEC_16_ID)
                {
                    throw new InvalidDataException($"Unsupported codec ID: {codecId}");
                }

                // Read number of data records
                var numberOfRecords = reader.ReadByte();

                var packet = new AVLDataPacket
                {
                    CodecId = codecId,
                    NumberOfRecords1 = numberOfRecords
                };

                // Read AVL data records
                for (int i = 0; i < numberOfRecords; i++)
                {
                    packet.AVLData.Add(ParseAVLDataRecord(reader, codecId, imei));
                }

                // Read number of data records again (should match)
                packet.NumberOfRecords2 = reader.ReadByte();
                if (packet.NumberOfRecords1 != packet.NumberOfRecords2)
                {
                    throw new InvalidDataException("Record count mismatch in AVL data packet");
                }

                // Read CRC
                packet.CRC = reader.ReadBytes(CRC_LENGTH);

                return packet;
            }
        }

        private AVLData ParseAVLDataRecord(BinaryReader reader, byte codecId, string imei)
        {
            var record = new AVLData();

            #region custom fields, those fileds are NOT included in the protocol
            record.IMEI = imei;
            record.CodecId = codecId;
            record.ReceivedTime = DateTime.Now;
            #endregion
            // Read timestamp (milliseconds since 1970)
            //var timestampMs = reader.ReadInt64();
            var timestampMs = BinaryPrimitives.ReadInt64BigEndian(reader.ReadBytes(8));
            record.Timestamp = DateTimeOffset.FromUnixTimeMilliseconds(timestampMs).DateTime;

            // Read priority
            record.Priority = reader.ReadByte();

            // Read GPS element
            record.GPS = ParseGPSElement(reader);

            // Read IO element
            record.IO = ParseIOElement(reader, codecId);

            return record;
        }

        private GPSElement ParseGPSElement(BinaryReader reader)
        {
            var gps = new GPSElement();

            // Read longitude (4 bytes)
            var longitudeRaw = reader.ReadInt32();
            gps.Longitude = longitudeRaw / 10000000.0;

            // Read latitude (4 bytes)
            var latitudeRaw = reader.ReadInt32();
            gps.Latitude = latitudeRaw / 10000000.0;

            // Read altitude (2 bytes)
            gps.Altitude = reader.ReadInt16();

            // Read angle (2 bytes)
            gps.Angle = reader.ReadInt16();

            // Read satellites (1 byte)
            gps.Satellites = reader.ReadByte();

            // Read speed (2 bytes)
            gps.Speed = reader.ReadInt16();

            // Determine if GPS data is valid
            gps.IsValid = gps.Speed != 0 && gps.Satellites > 0;

            return gps;
        }

        private IOElement ParseIOElement(BinaryReader reader, byte codecId)
        {
            var io = new IOElement();

            // Read Event IO ID (2 byte)
            io.EventIOId = BinaryPrimitives.ReadInt16BigEndian(reader.ReadBytes(2));

            // Read Total IO (2 byte)
            io.TotalIO = BinaryPrimitives.ReadInt16BigEndian(reader.ReadBytes(2));

            // Parse based on codec version
            if (codecId == CODEC_8_ID || codecId == CODEC_16_ID)
            {
                // Codec 8 and 16 have slightly different IO element structure
                ParseCodec8IOElement(reader, io);
            }
            else if (codecId == CODEC_8_EXTENDED_ID)
            {
                ParseCodec8ExtendedIOElement(reader, io);
            }

            return io;
        }

        private void ParseCodec8IOElement(BinaryReader reader, IOElement io)
        {
            // Read N1 (1 byte IO count)
            var n1 = reader.ReadByte();
            for (int i = 0; i < n1; i++)
            {
                io.OneByteIO.Add(new IOProperty
                {
                    Id = reader.ReadByte(),
                    Value = reader.ReadByte()
                });
            }

            // Read N2 (2 bytes IO count)
            var n2 = reader.ReadByte();
            for (int i = 0; i < n2; i++)
            {
                io.TwoBytesIO.Add(new IOProperty
                {
                    Id = reader.ReadByte(),
                    Value = reader.ReadInt16()
                });
            }

            // Read N4 (4 bytes IO count)
            var n4 = reader.ReadByte();
            for (int i = 0; i < n4; i++)
            {
                io.FourBytesIO.Add(new IOProperty
                {
                    Id = reader.ReadByte(),
                    Value = reader.ReadInt32()
                });
            }

            // Read N8 (8 bytes IO count) - only in Codec 16
            if (io.TotalIO > n1 + n2 + n4)
            {
                var n8 = (byte)(io.TotalIO - n1 - n2 - n4);
                for (int i = 0; i < n8; i++)
                {
                    io.EightBytesIO.Add(new IOProperty
                    {
                        Id = reader.ReadByte(),
                        Value = reader.ReadInt64()
                    });
                }
            }
        }

        private void ParseCodec8ExtendedIOElement(BinaryReader reader, IOElement io)
        {
            // Read N1 (1 byte IO count) - 2 bytes for ID in extended
            //var n1 = reader.ReadInt16();
            var n1 = BinaryPrimitives.ReadInt16BigEndian(reader.ReadBytes(2));
            for (int i = 0; i < n1; i++)
            {
                io.OneByteIO.Add(new IOProperty
                {
                    Id = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(2)),
                    Value = reader.ReadByte()
                });
            }

            // Read N2 (2 bytes IO count) - 2 bytes for ID in extended
            //var n2 = reader.ReadInt16();
            var n2 = BinaryPrimitives.ReadInt16BigEndian(reader.ReadBytes(2));
            for (int i = 0; i < n2; i++)
            {
                io.TwoBytesIO.Add(new IOProperty
                {
                    Id = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(2)),
                    Value = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(2))
                });
            }

            // Read N4 (4 bytes IO count) - 2 bytes for ID in extended
            //var n4 = reader.ReadInt16();
            var n4 = BinaryPrimitives.ReadInt16BigEndian(reader.ReadBytes(2));
            for (int i = 0; i < n4; i++)
            {
                io.FourBytesIO.Add(new IOProperty
                {
                    Id = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(2)),
                    Value = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(4))
                });
            }

            // Read N8 (8 bytes IO count) - 2 bytes for ID in extended
            //var n8 = reader.ReadInt16();
            var n8 = BinaryPrimitives.ReadInt16BigEndian(reader.ReadBytes(2));
            for (int i = 0; i < n8; i++)
            {
                io.EightBytesIO.Add(new IOProperty
                {
                    Id = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(2)),
                    Value = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(8))
                });
            }

            //Read Nx (x bytes IO count) - x bytes for ID in extended
            var nx = BinaryPrimitives.ReadUInt16BigEndian(reader.ReadBytes(2));

        }

        public byte[] CreateAcknowledgement(int numberOfRecords)
        {
            using (var stream = new MemoryStream())
            using (var writer = new BinaryWriter(stream))
            {
                // Preamble (4 zero bytes)
                writer.Write((byte)0x00);
                writer.Write((byte)0x00);
                writer.Write((byte)0x00);
                writer.Write((byte)numberOfRecords);

                return stream.ToArray();
            }
        }
    }
}
